django-structlog 4.0未満での middlewareの注意点
django-structlog (4.0未満)の公式手順では以下の様にmiddlewareを設定する
code:settings.py
MIDDLEWARE = [
# ...
'django_structlog.middlewares.RequestMiddleware',
]
middlewareの実装では logger.bind(user_id=user_id) としてい
https://github.com/jrobichaud/django-structlog/blob/ca78e4894e97ab606e05628e097ad9b8ba833be4/django_structlog/middlewares/request.py#L105-L111
code:python
@staticmethod
def bind_user_id(request):
if hasattr(request, "user") and request.user is not None:
user_id = request.user.pk
if isinstance(user_id, uuid.UUID):
user_id = str(user_id)
logger.bind(user_id=user_id)
上記は context_class=structlog.threadlocal.wrap_dict(dict) に依存する実装になっているが、これは logger.bind の値を暗黙的にthreadlocalに共有する。
この設定では、以下の不都合が発生する
code:python
logger.info('message') # -> {'event': 'message'}
logger2 = logger.bind(foo='追加情報')
logger2.info('message') # -> {'event': 'message', 'foo': '追加情報'}
logger.info('message') # -> {'event': 'message', 'foo': '追加情報'}
# 追加情報fooは元のloggerの方では出力されないはずだったのに、出力されてしまってる
logger3 = structlog.get_logger()
logger3.info('message') # -> {'event': 'message', 'foo': '追加情報'}
# 別モジュールのloggerでも出力されてしまう
これでは局所的なbindができない
structlog.threadlocal.wrap_dict は、structlog本体で 2021/07/16 に非推奨となった
doc: https://www.structlog.org/en/stable/api.html?highlight=bind_threadlocal#old-approach
commit: https://github.com/hynek/structlog/commit/f55319b9672eb63f299c70f4c033d498c41a8a6f#diff-0ee295c29def16f570f86378ef[…]64d415e731d5d8ea2e67ce16f68R140
対策として、以下の2つを行うとよい
1. settings で context_class を指定しない
2. RequestMiddleware クラスを継承し、 bind_user_id を以下の実装に差し替える
code:python
from django_structlog.middlewares import RequestMiddleware
from structlog.threadlocal import bind_threadlocal
class RequestLogMiddleware(RequestMiddleware):
@staticmethod
def bind_user_id(request):
if hasattr(request, "user") and request.user is not None:
user_id = request.user.pk
if isinstance(user_id, uuid.UUID):
user_id = str(user_id)
bind_threadlocal(user_id=user_id) # この行を変更